package cn.gov.mwr.szy206;

import java.io.IOException;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.Date;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;

import cn.gov.mwr.szy206.body.BodyDown;
import cn.gov.mwr.szy206.utils.ByteUtil;
import cn.gov.mwr.szy206.utils.CrcUtil;
import cn.gov.mwr.szy206.utils.DateUtil;

/**
 * 水资源构造器，在此构造
 * 
 * @ClassName: SzyBuilder
 * @Description: TODO
 * @author lipujun
 * @date Jun 6, 2013
 * 
 */
public class SzyBuilder {

	public static String STCD = "42010111067";

	public SzyMessage newDownCommand(String stcd, byte C, byte AFN) {

		BodyDown downData = new BodyDown();
		downData.setAFN(AFN);

		switch (AFN) {
		case (byte) 0x02:// 链路检测
			downData.setData(new byte[] { (byte) 0xF0 });
			break;
		case (byte) 0x10:// 设置遥测终端、中继站地址
			downData.setData(SzyBuilder.newStcd(STCD));
			downData.setPw(SzyBuilder.newPW(0, 999));
			downData.setTp(SzyBuilder.newTP(0));
			break;
		case (byte) 0x11:// 设置遥测终端、中继站时钟
			downData.setData(SzyBuilder.newClock());
			downData.setPw(SzyBuilder.newPW(0, 999));
			downData.setTp(SzyBuilder.newTP(0));
			break;
		case (byte) 0x12:// 设置遥测终端工作模式
			downData.setData(new byte[] { 0x00 });
			// 工作模式类型=00B，设置遥测终端在兼容工作状态；
			// 工作模式类型=01H，设置遥测终端在自报工作状态；
			// 工作模式类型=02H，设置遥测终端在查询/应答工作状态；
			// 工作模式类型=03H，遥测终端在调试/维修状态
			downData.setPw(SzyBuilder.newPW(0, 999));
			downData.setTp(SzyBuilder.newTP(0));
			break;
		case (byte) 0xA1:// 设置遥测终端的数据自报种类及时间间隔
			// downData.setData();
			downData.setPw(SzyBuilder.newPW(0, 999));
			downData.setTp(SzyBuilder.newTP(0));
			break;
		case (byte) 0xA0:// 设置遥测站需查询的实时数据种类
			downData.setData(new byte[] {});// 2个字节
			downData.setPw(SzyBuilder.newPW(0, 999));
			downData.setTp(SzyBuilder.newTP(0));
			break;
		case (byte) 0x15:// 设置遥测终端本次充值量
			downData.setData(new byte[] {});// 4个字节
			downData.setPw(SzyBuilder.newPW(0, 999));
			downData.setTp(SzyBuilder.newTP(0));
			break;
		case (byte) 0x16:// 设置遥测终端剩余水量报警值
			downData.setData(new byte[] {});// 3个字节
			downData.setPw(SzyBuilder.newPW(0, 999));
			downData.setTp(SzyBuilder.newTP(0));
			break;
		case (byte) 0x17:// 设置遥测终端的水位基值、水位上下限
			downData.setData(new byte[] {});// 7 * N个字节
			downData.setPw(SzyBuilder.newPW(0, 999));
			downData.setTp(SzyBuilder.newTP(0));
			break;
		case (byte) 0x18:// 设置遥测终端水压上、下限
			downData.setData(new byte[] {});// 8 * N个字节
			downData.setPw(SzyBuilder.newPW(0, 999));
			downData.setTp(SzyBuilder.newTP(0));
			break;
		case (byte) 0x19:// 设置遥测终端水质参数种类、上限值
			downData.setData(new byte[] {});// 5 + n * 4 + 1
			downData.setPw(SzyBuilder.newPW(0, 999));
			downData.setTp(SzyBuilder.newTP(0));
			break;
		case (byte) 0x1A:// 设置遥测终端水质参数种类、下限值

			downData.setData(new byte[] {});// 数据域（5＋N*4＋1 个字节）
			downData.setPw(SzyBuilder.newPW(0, 999));
			downData.setTp(SzyBuilder.newTP(0));
			break;
		case (byte) 0x1B:// 设置终端站水量的表底（初始）值
			downData.setData(new byte[] {});// N*5 个字节BCD
			downData.setPw(SzyBuilder.newPW(0, 999));
			downData.setTp(SzyBuilder.newTP(0));
			break;
		case (byte) 0x1C:// 设置遥测终端转发中继引导码长

			break;
		case (byte) 0x1D:// 设置中继站转发终端地址

			break;
		case (byte) 0x1E:// 设置中继站工作机自动切换，自报状态

			break;
		case (byte) 0x1F:// 设置遥测终端流量参数上限值
			downData.setData(new byte[] {});// N*5 个字节BCD
			downData.setPw(SzyBuilder.newPW(0, 999));
			downData.setTp(SzyBuilder.newTP(0));
			break;
		case (byte) 0x20:// 设置遥测终端雨量启报阈值及固态存储时间段间隔
			downData.setData(new byte[] {});// N*5 个字节BCD
			downData.setPw(SzyBuilder.newPW(0, 999));
			downData.setTp(SzyBuilder.newTP(0));
		case (byte) 0x30:// 置遥测终端IC 卡功能有效

			break;
		case (byte) 0x31:// 取消遥测终端IC 卡功能

			break;
		case (byte) 0x32:// 定值控制投入

			break;
		case (byte) 0x33:// 定值控制退出

			break;
		case (byte) 0x34:// 定值量设定

			break;
		case (byte) 0x90:// 复位遥测终端参数和状态
		case (byte) 0x91:// 清空遥测终端历史数据单元
		case (byte) 0x92:// 遥控启动水泵或阀门/闸门
		case (byte) 0x93:// 遥控关闭水泵或阀门/闸门
		case (byte) 0x94:// 遥控终端或中继站通信机切换
		case (byte) 0x95:// 遥控中继站工作机切换
		case (byte) 0x96:// 修改遥测终端密码

		case (byte) 0xB0:// 查询遥测终端实时值
		case (byte) 0xB1:// 查询遥测终端固态存储数据
		case (byte) 0xB2:// 查询遥测终端内存自报数据

		case (byte) 0x81:// 随机自报报警数据
		case (byte) 0x82:// 人工置数
		case (byte) 0xC0:// 遥测终端自报实时数据
		}

		int[] c = SzyParser.parseControl(C);

		SzyMessageBody body = new SzyMessageBody();
		body.setControl(SzyBuilder.newControl(0, 0, c[1], c[2]));
		body.setAddress(SzyBuilder.newStcd(stcd));

		return newMessage(body);
	}

	public BodyDown newQueryCommand(byte AFN) {
		BodyDown downCmd = new BodyDown();
		switch (AFN) {
		case (byte) 0x50:// 查询遥测终端、中继站地址
		case (byte) 0x51:// 查询遥测终端、中继站时钟
		case (byte) 0x52:// 查询遥测终端工作模式
		case (byte) 0x53:// 查询遥测终端的数据自报种类及时间间隔
		case (byte) 0x54:// 查询遥测终端需查询的实时数据种类
		case (byte) 0x55:// 查询遥测终端最近成功充值量和现有剩余水量
		case (byte) 0x56:// 查询遥测终端剩余水量和报警值
		case (byte) 0x57:// 查询遥测终端水位基值、水位上下限
		case (byte) 0x58:// 查询遥测终端水压上、下限
		case (byte) 0x59:// 查询遥测终端水质参数种类、上限值
		case (byte) 0x5A:// 查询遥测终端水质参数种类、下限值
		case (byte) 0x5D:// 查询遥测终端的事件记录
		case (byte) 0x5E:// 查询遥测终端状态和报警状态
		case (byte) 0x5F:// 查询水泵电机实时工作数据
		case (byte) 0x60:// 查询遥测终端转发中继引导码长
		case (byte) 0x61:// 查询遥测终端图像记录
		case (byte) 0x62:// 查询中继站转发终端地址
		case (byte) 0x63:// 查询中继站工作机状态和切换记录
		case (byte) 0x64:// 查询遥测终端流量参数上限值
		case (byte) 0xB0:// 查询遥测终端实时值（AFN=B0H)
			downCmd.setAFN(AFN);
		}

		return downCmd;
	}

	/**
	 * 生成消息HEADER
	 * 
	 * @return
	 */
	public static SzyMessageHeader newHeader() {
		SzyMessageHeader header = new SzyMessageHeader();

		return header;
	}

	/**
	 * 生成遥测站地
	 * 
	 * @param data
	 * @return
	 */
	public static byte[] newStcd(String stcd) {

		if (stcd.length() == 12) {
			String addvcd = stcd.substring(0, 6);
			String addr = stcd.substring(6, stcd.length());
			
			byte[] addvcdbyte = ByteUtil.str2Bcd(addvcd);
			// 将后续的数字转换为HEX
			int addrNum = Integer.parseInt(addr);
			byte[] addrbyte = ByteUtil.shortToBytes((short) addrNum);
			ArrayUtils.reverse(addrbyte);
			
			byte[] stcdBytes = new byte[5];
			System.arraycopy(addvcdbyte, 0, stcdBytes, 0, 3);
			System.arraycopy(addrbyte, 0, stcdBytes, 3, 2);

			return stcdBytes;
		}

		if (stcd.length() == 8) {
			// 左补零
			stcd = StringUtils.leftPad(stcd, 10, "0");
		}

		if (StringUtils.startsWith(stcd, "00")) {
			// 按《SL 502》标准协议
			return ByteUtil.str2Bcd(stcd);

		} else {
			// 前三个字节A5、A4、A3采用GB2260—2007规定的行政区划代码的前6位，
			// System.out.println("stcd GB2260—2007");
			byte[] stcdBytes = new byte[5];

			String addvcd = stcd.substring(0, 6);
			String addr = stcd.substring(6, stcd.length());

			byte[] addvcdbyte = ByteUtil.str2Bcd(addvcd);

			// 后两个字节A2、A1为遥测站地址自定义段，采用HEX码，中心站解码时还原为3个字节BCD码；
			// 遥测站地址自定义1～60000,中继站地址自定义60001～65534

			byte[] addrbyte = ByteUtil.HexStringToBinary(addr);

			System.arraycopy(addvcdbyte, 0, stcdBytes, 0, 3);
			System.arraycopy(addrbyte, 0, stcdBytes, 3, 2);

			return stcdBytes;

		}

	}

	/**
	 * 生成控制域，占用一个字节
	 * 
	 * @return
	 */
	public static byte newControl(int DIR, int DIV, int FCB, int FUNC) {
		byte bDir = (byte) (0x01 & DIR);// (0x80 & DIR);
		byte bDiv = (byte) (0x01 & DIV);// (0x40 & DIV);
		byte bFcb = (byte) (0x03 & FCB);// (0x30 & FCB);
		byte bFunc = (byte) (0x0F & FUNC);

		return (byte) ((bDir << 7) | (bDiv << 6) | (bFcb << 4) | bFunc);
	}

	/**
	 * 生成密码：
	 * 
	 * 1) 密码PW 用于重要下行报文中，由2 字节组成，PW 是由中心站将密钥按系统约定的密码算
	 * 法产生，并在中心站发送的报文中下发给终端，由终端进行校验，通过则响应中心站命令， 反之否认；
	 * 
	 * 2) 密码长度由两个字节组成：第一个字节前半个字节为密钥算法，采用BCD 编码，取值范围 0～9；第一个字节后半字节和第二个字节共12
	 * 位为密钥，采用BCD 编码，取值范围0～ 999。终端根据密钥及密钥算法，计算出密码，然后与终端持有的密码进行比对验证，密
	 * 码相匹配，则命令有效，否则命令无效；
	 * 
	 * @param method
	 *            密钥算法(BCD 编码)，取值范围 0～9；
	 * @param secret
	 *            密钥（BCD 编码），取值范围0～ 999
	 */

	public static byte[] newPW(int method, int secret) {
		if (method < 0)
			method = 0;
		if (method > 9)
			method = 9;
		byte tempMethod = (byte) (method & 0x0F);
		// int tempSecret = (secret & 0x0FFF);

		if (secret < 0)
			secret = 0;
		if (secret > 999)
			secret = 999;

		String tmpsecret = Integer.toString(secret);
		tmpsecret = method + StringUtils.leftPad(tmpsecret, 3, "0");

		byte[] byteSecret = ByteUtil.str2Bcd(tmpsecret);

		// byteSecret[0] = (byte) ((tempMethod << 4) | (byteSecret[0] & 0x0F));
		ArrayUtils.reverse(byteSecret);
		return byteSecret;
	}

	/**
	 * 构造时间标签Tp
	 * 
	 * @param delay
	 *            允许发送传输延时时间
	 * @return
	 */
	public static byte[] newTP(int delay) {
		if (delay > 255)
			delay = 255;

		SimpleDateFormat sdf = new SimpleDateFormat("ddHHmmss");// yyyyMMddHHmmss
		String datestr = sdf.format(new Date(System.currentTimeMillis()));
		byte[] tp = new byte[5];
		byte[] date = ByteUtil.str2Bcd(datestr);

		tp[0] = date[3];
		tp[1] = date[2];
		tp[2] = date[1];
		tp[3] = date[0];
		tp[4] = (byte) (delay & 0xFF);

		return tp;
	}

	/**
	 * 设置时钟
	 * 
	 * 应用场景：
	 * 
	 * 1、设置遥测终端或中继站时钟（AFN=11H）
	 * 
	 */
	public static byte[] newClock() {
		SimpleDateFormat sdf = new SimpleDateFormat("yyMMddHHmmss");// yyyyMMddHHmmss
		Date curDate = new Date(System.currentTimeMillis());
		String datestr = sdf.format(curDate);

		byte[] date = ByteUtil.str2Bcd(datestr);

		// 得出当前的星期天数
		int week = DateUtil.week(curDate);

		byte d2low = (byte) (date[1] & 0x1F);
		byte d2high = (byte) ((week & 0x07) << 5);
		date[1] = (byte) (d2high | d2low);
		ArrayUtils.reverse(date);
		return date;
	}

	/**
	 * 询流量（水量）参数：流量（水量）仪表数量为N。每个流量数据为5 个字节压缩BCD。
	 * 
	 * 取值范围为-999999.999～+999999.999，单位为m3/s，m3/h（水资源）。
	 * 
	 * @param delay
	 *            允许发送传输延时时间
	 * @return
	 */
	public static byte[] newS(double value) {

		String dstr = Double.toString(value);
		int pos = dstr.indexOf(".");
		String dleft = "";
		String dright = "";
		if (pos != -1) {
			dleft = dstr.substring(0, pos);
			dright = dstr.substring(pos + 1, dstr.length());
		} else {
			dleft = dstr;
			dright = "000";
		}
		StringUtils.leftPad(dleft, 6, "0");
		StringUtils.rightPad(dright, 3, "0");

		byte[] data = ByteUtil.str2Bcd(dleft + dright);
		byte[] d = new byte[5];

		d[0] = data[0];
		d[1] = data[1];
		d[2] = data[2];
		d[3] = data[3];

		if (value < 0) {
			d[4] = (byte) ((byte) (data[4] & 0x0F) & (byte) 0xF0);
		} else {
			d[4] = (byte) (data[4] & 0x0F);
		}

		return d;
	}

	/**
	 * 新水位 值
	 * 
	 * @param value
	 * @param length
	 * @return
	 */
	public static byte[] newR(double value, int length) {
		// 记录是否小于0的值
		boolean flag = false;
		if (value < 0) {
			flag = true;
			value = value * -1;
		}

		// 1、格式化
		DecimalFormat df = new DecimalFormat("##.00");
		String dstr = df.format(value);

		String dleft = "";
		String dright = "";
		String newstr = "";

		// 2、去小数点
		int pos = dstr.indexOf(".");
		if (pos != -1) {
			dleft = dstr.substring(0, pos);
			dright = dstr.substring(pos + 1, dstr.length());
			newstr = dleft + dright;
		} else {
			newstr = dstr;
		}

		// 3、左补0
		newstr = StringUtils.leftPad(newstr, length, "0");

		// 4、转换值为字节，表示原值小于0，需要将第一位标识为负数
		byte[] data = ByteUtil.str2Bcd(newstr);

		if (flag) {
			data[4] = (byte) (data[4] | (byte) 0x80);// 进行或运算，两个位只要有一个为1，那么结果就是1，否则就为0
														// ,因为是负数，所以需要将此值转换为
		}

		ArrayUtils.reverse(data);

		return data;

	}

	/**
	 * 水质参数值
	 * 
	 * @param value
	 * @param length
	 * @return
	 */
	public static byte[] newSZ(double value, int length, int decimal) {
		// 记录是否小于0的值

		String sDecimal = "";
		for (int i = 0; i < decimal; i++) {
			sDecimal = sDecimal + "0";
		}

		// 1、格式化
		DecimalFormat df = new DecimalFormat("####." + sDecimal);
		String dstr = df.format(value);

		String dleft = "";
		String dright = "";
		String newstr = "";

		// 2、去小数点
		int pos = dstr.indexOf(".");
		if (pos != -1) {
			dleft = dstr.substring(0, pos);
			dright = dstr.substring(pos + 1, dstr.length());
			newstr = dleft + dright;
		} else {
			newstr = dstr;
		}

		// 3、左补0
		newstr = StringUtils.leftPad(newstr, length, "0");

		// 4、转换值为字节，表示原值小于0，需要将第一位标识为负数
		byte[] data = ByteUtil.str2Bcd(newstr);

		ArrayUtils.reverse(data);

		return data;

	}

	/**
	 * 构造水资源消息体
	 * 
	 * @param msgBody
	 * @return
	 */
	public static SzyMessage newMessage(SzyMessageBody msgBody) {
		// 获取消息体，并计算CRC
		byte[] crc = CrcUtil.crc8Check(msgBody.getContent());

		SzyMessageHeader header = new SzyMessageHeader();
		header.setBodySize(msgBody.getLength());// 此处若大于255，则需要拆分包

		SzyMessage message = new SzyMessage();
		message.setHeader(header);
		message.setBody(msgBody);
		message.setCRC(crc);
		message.setEOF(SzySymbol.EOF);

		return message;
	}

	public static byte[] toByte(IMessage message) {
		// StringBuffer sb = new StringBuffer();
		SzyMessageHeader header = (SzyMessageHeader) message.getHeader();
		SzyMessageBody body = (SzyMessageBody) message.getBody();
		// sb.append(ByteUtil.toHexString(header.getStartBit()));
		// sb.append(ByteUtil.toHexString(header.getBodySize()));
		// sb.append(ByteUtil.toHexString(header.getBodyStartBit()));
		// sb.append(ByteUtil.toHexString(body.getContent()));
		// sb.append(ByteUtil.toHexString(message.getCRC()));
		// sb.append(ByteUtil.toHexString(message.getEOF()));

		byte[] content = new byte[3 + body.getContent().length + 2];

		int pos = 0;
		System.arraycopy(header.getStartBit(), 0, content, pos,
				header.getStartBitLen());
		pos = pos + header.getStartBitLen();

		System.arraycopy(header.getBodySize(), 0, content, pos,
				header.getBodySizeLen());
		pos = pos + header.getBodySizeLen();

		System.arraycopy(header.getBodyStartBit(), 0, content, pos,
				header.getBodyStartBitLen());
		pos = pos + header.getBodyStartBitLen();

		// -------------------------------------------------
		System.arraycopy(body.getContent(), 0, content, pos,
				body.getContent().length);
		pos = pos + body.getContent().length;

		// -------------------------------------------------
		System.arraycopy(message.getCRC(), 0, content, pos,
				message.getCRC().length);
		pos = pos + message.getCRC().length;

		System.arraycopy(new byte[] { message.getEOF() }, 0, content, pos, 1);

		return content;
	}

	public static String toHexString(IMessage message) {
		StringBuffer sb = new StringBuffer();
		SzyMessageHeader header = (SzyMessageHeader) message.getHeader();
		IMessageBody body = (IMessageBody) message.getBody();
		sb.append(ByteUtil.toHexString(header.getStartBit()));
		sb.append(ByteUtil.toHexString(header.getBodySize()));
		sb.append(ByteUtil.toHexString(header.getBodyStartBit()));
		sb.append(ByteUtil.toHexString(body.getContent()));
		sb.append(ByteUtil.toHexString(message.getCRC()));
		sb.append(ByteUtil.toHexString(message.getEOF()));
		return sb.toString().toUpperCase();
	}

	public static String toHexString(IMessageHeader header) {
		StringBuffer sb = new StringBuffer();

		sb.append(ByteUtil.toHexString(header.getStartBit()));
		sb.append(ByteUtil.toHexString(header.getBodySize()));
		sb.append(ByteUtil.toHexString(header.getBodyStartBit()));

		return sb.toString();
	}

	/**
	 * 测试方法
	 * 
	 * @param args
	 * @throws IOException
	 */
	public static void main(String[] args) throws IOException {

		byte[] pw = SzyBuilder.newPW(8, 876);
		System.out.println(ByteUtil.toHexString(pw));

		byte c = SzyBuilder.newControl(0, 1, 3, 16);
		System.out.println(ByteUtil.toBinaryString(c));

		byte[] date = SzyBuilder.newClock();
		System.out.println(ByteUtil.toHexString(date));

		byte[] stcd = SzyBuilder.newStcd("42010111067");
		System.out.println(ByteUtil.toHexString(stcd));

		String bstr = "1010101010101010";

		// for (int i = 0, len = bstr.length(); i < len; i++) {
		// // System.out.println(i + "" + bstr.substring(i, i + 1));
		// String flag = bstr.substring(i, i + 1);
		// System.out.println(i + " " + i * 2 + "   " + (i * 2 + 1));
		// }

	}
}
